package cc.aliza.lib.mongo.core.decoder;

import cc.aliza.lib.mongo.core.annotations.EmbedList;
import cc.aliza.lib.mongo.core.utils.DataType;
import cc.aliza.lib.mongo.core.utils.FieldUtil;
import cc.aliza.lib.mongo.core.utils.MapperUtil;
import org.bson.Document;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 『给时光以生命·给岁月以文明』
 *
 * @author Zhang Jing (byrdkm17@gmail.com).
 */
@SuppressWarnings("unchecked")
public class EmbedListDecoder extends AbstractDecoder {

    public EmbedListDecoder(Document document, Field field) {
        super(field);
        EmbedList embedList = field.getAnnotation(EmbedList.class);
        value = document.get(getFieldRealName(embedList.name()));
    }

    @Override
    public void decode(Object obj) {
        Class<?> type = field.getType();
        if (type.isArray()) {
            decodeArray(obj, type.getComponentType());
        } else {
            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            Type[] types = parameterizedType.getActualTypeArguments();
            int len = types.length;
            if (len == 1) {
                Class<?> clazz;
                if (types[0] instanceof WildcardType) {
                    clazz = (Class) ((WildcardType) types[0]).getUpperBounds()[0];
                } else {
                    clazz = (Class) types[0];
                }
                decodeCollection(obj, clazz);
            }
            if (len == 2) {
                decodeMap(obj, (Class<?>) types[1]);
            }
        }
    }

    private void decodeArray(Object obj, Class<?> clazz) {
        List list = (ArrayList) value;
        int size = list.size();
        Object result = Array.newInstance(clazz, size);
        for (int i = 0; i < size; i++) {
            Array.set(result, i, MapperUtil.fromDocument(FieldUtil.getRealType(field, clazz, list.get(i)), (Document) list.get(i)));
        }
        FieldUtil.set(obj, field, result);
    }

    private void decodeCollection(Object obj, Class<?> clazz) {
        List list = (ArrayList) value;
        List result = (List) list.stream()
                .filter(o -> o != null)
                .map(o -> MapperUtil.fromDocument(FieldUtil.getRealType(field, clazz, o), (Document) o))
                .collect(Collectors.toList());

        Class type = field.getType();
        if (DataType.isListType(type)) {
            FieldUtil.set(obj, field, result);
        }
        if (DataType.isSetType(type)) {
            FieldUtil.set(obj, field, new HashSet<>(result));
        }
        if (DataType.isQueueType(type)) {
            FieldUtil.set(obj, field, new LinkedList<>(result));
        }
    }

    private void decodeMap(Object obj, Class<?> clazz) {
        Map map = (Map) value;
        Map result = new HashMap<>();
        map.entrySet().stream()
                .filter(o -> ((Map.Entry) o).getValue() != null)
                .forEach(o -> {
                    Map.Entry entry = (Map.Entry) o;
                    result.put(entry.getKey(), MapperUtil.fromDocument(FieldUtil.getRealType(field, clazz, entry.getValue()), (Document) entry.getValue()));
                });
        FieldUtil.set(obj, field, result);
    }
}
